﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Script.Serialization;

namespace SplendidCRM.Ext
{
    public static class Mvc
    {
        public static Object GetParam = new object();
        public static Object PostParam = new object();
        public static Object RequestParam = new object();


        class JsonEnumerableSerailizer<TKey, TValue>
        {
            public JsonEnumerableSerailizer(StringBuilder builder, Type type, IEnumerable<KeyValuePair<TKey, TValue>> e, Boolean isJavaScript, NamingStyle defaultNamingStyle)
            {
                var naming = type.GetCustomAttributes(typeof (NamingAttribute), true).OfType<NamingAttribute>().FirstOrDefault() ?? new NamingAttribute(defaultNamingStyle);
                builder.Append('{');
                var first = true;
                foreach (var p in e)
                {
                    if (!first) builder.Append(",");
                    builder
                        .Append(Json(ConvertName(p.Key, naming.Style), isJavaScript))
                        .Append(':')
                        .Append(Json(p.Value, isJavaScript, defaultNamingStyle))
                        ;
                    first = false;
                }
                builder.Append('}');
            }
        }

        public static RouteValueDictionary Merge(this Object value1, Object value2)
        {
            return Merge(value1, value2, true);
        }

        private static String ConvertName(Object value, NamingStyle style)
        {
            if (value == null) return String.Empty;
            if (style == NamingStyle.Default) return value.ToString();
            var result = value.ToString();
            
            if ((style & NamingStyle.CamelCase) == NamingStyle.CamelCase)
            {
                var i = result.IndexOfAny(new[] {'_', '-', ' '});
                if (i == -1)
                {
                    return result.Substring(0, 1).ToLower() + result.Substring(1);
                }
                var builder = new StringBuilder();
                var first = true;
                var toUpper = false;
                foreach (var c in result)
                {
                    if (first)
                    {
                        builder.Append(Char.ToLower(c));
                    }
                    else if (c == '_' || c == '-' || c == ' ')
                    {
                        toUpper = true;
                    }
                    else
                    {
                        if (toUpper)
                        {
                            builder.Append(Char.ToUpper(c));
                            toUpper = false;
                        }
                        else
                        {
                            builder.Append(Char.ToLower(c));
                        }
                    }
                    first = false;
                }
                result = builder.ToString();
            }
            else
            {
                if ((style & NamingStyle.LowerCase) == NamingStyle.LowerCase)
                {
                    result = result.ToLower();
                }
                if ((style & NamingStyle.RemoveSeparators) == NamingStyle.RemoveSeparators) result = result.Replace("_", String.Empty).Replace("-", String.Empty);
            }
            return result;
        }

        public static IEnumerable<dynamic> ToObjectList<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> values, String keyProperty = "key", String valueProperty = "value")
        {
            return values.Select(pair => new Query.Result(new Dictionary<String, Object>
                                                              {
                                                                  {keyProperty, pair.Key},
                                                                  {valueProperty, pair.Value}
                                                              }));
        }

        public static JsonResult JsonResult(this Object obj, Boolean isJavaScript = false, NamingStyle defaultNamingStyle = NamingStyle.Default, Boolean allowGet = false)
        {
            return new CustomJsonResult(obj, allowGet) {IsJavaScript = isJavaScript, NamingStyle = defaultNamingStyle};
        }

        public static String Json(this Object obj, NamingStyle defaultNamingStyle)
        {
            return Json(obj, false, defaultNamingStyle);
        }

        public static String Json(this Object obj, Boolean isJavaScript = false, NamingStyle defaultNamingStyle = NamingStyle.Default)
        {
            var builder = new StringBuilder();
            if (obj == null || obj is DBNull)
            {
                builder.Append("null");
            }
            else
            {
                if (obj is String)
                {
                    builder.Append(new JavaScriptSerializer().Serialize(obj));
                }
                else if(obj is DateTime)
                {
                    if (isJavaScript)
                    {
                        var d = (DateTime) obj;
                        builder.AppendFormat("new Date({0},{1},{2},{3},{4},{5},{6})", d.Year, d.Month - 1, d.Day, d.Hour, d.Minute, d.Second, d.Millisecond);
                    }
                    else
                        builder.Append(new JavaScriptSerializer().Serialize(obj));
                }
                else if (obj is IDictionary)
                {
                    var naming = obj.GetType().GetCustomAttributes(typeof (NamingAttribute), true).OfType<NamingAttribute>().FirstOrDefault() ?? new NamingAttribute(defaultNamingStyle);
                    var dic = (IDictionary)obj;
                    builder.Append('{');
                    var first = true;
                    foreach (DictionaryEntry p in dic)
                    {
                        if (!first) builder.Append(",");
                        builder
                            .Append(Json(ConvertName(p.Key, naming.Style), isJavaScript))
                            .Append(':')
                            .Append(Json(p.Value, isJavaScript, defaultNamingStyle))
                            ;
                        first = false;
                    }
                    builder.Append('}');
                }
                else
                {
                    var type = obj.GetType();
                    var interfaces = type.GetInterfaces();
                    var keyValuePairType = typeof (KeyValuePair<,>);
                    var enumerableType = typeof (IEnumerable<>);
                    var isDictionary = false;
                    foreach (var i in interfaces)
                    {
                        if (!i.IsGenericType || i.GetGenericTypeDefinition() != enumerableType) continue;
                        var elementType = i.GetGenericArguments()[0];
                        if (!elementType.IsGenericType || elementType.GetGenericTypeDefinition() != keyValuePairType) continue;
                        var args = elementType.GetGenericArguments();
                        Activator.CreateInstance(typeof (JsonEnumerableSerailizer<,>).MakeGenericType(args), builder, type, obj, isJavaScript, defaultNamingStyle);
                        isDictionary = true;
                        break;
                    }

                    if(!isDictionary)
                    {
                        if (obj is IEnumerable)
                        {
                            var e = (IEnumerable)obj;
                            builder.Append('[');
                            var first = true;
                            foreach (var v in e)
                            {
                                if (!first) builder.Append(",");
                                builder.Append(Json(v, isJavaScript, defaultNamingStyle));
                                first = false;
                            }
                            builder.Append(']');
                        }
                        else
                        {

                            if (type.IsValueType)
                            {
                                builder.Append(new JavaScriptSerializer().Serialize(obj));
                            }
                            else
                            {
                                var naming = type.GetCustomAttributes(typeof(NamingAttribute), true).OfType<NamingAttribute>().FirstOrDefault() ?? new NamingAttribute(defaultNamingStyle);
                                builder.Append('{');
                                var first = true;
                                var properties = type.GetFields(BindingFlags.Instance | BindingFlags.Public).Cast<MemberInfo>().Concat(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)).ToArray();

                                foreach (var p in properties)
                                {
                                    if (!first) builder.Append(",");
                                    var customNaming = p.GetCustomAttributes(typeof (NamingAttribute), true).OfType<NamingAttribute>().FirstOrDefault();
                                    builder
                                        .Append(Json(ConvertName(p.Name, (customNaming ?? naming).Style), isJavaScript))
                                        .Append(':');
                                    var field = p as FieldInfo;
                                    if (field != null)
                                    {
                                        builder.Append(Json(field.GetValue(obj), isJavaScript, defaultNamingStyle));
                                    }
                                    else
                                    {
                                        var property = (PropertyInfo) p;
                                        builder.Append(Json(property.GetValue(obj, new Object[0]), isJavaScript, defaultNamingStyle));
                                    }
                                    first = false;
                                }

                                builder.Append('}');
                            }
                        }
                    }

                    
                }
            }
            return builder.ToString();
        }

        public static RouteValueDictionary Merge(this Object value1, Object value2, Boolean overwrite)
        {
            var result = new RouteValueDictionary(GetRouteValues(value1));
            var dic = GetRouteValues(value2);
            foreach (var pair in dic)
            {
                if (overwrite)
                {
                    result[pair.Key] = pair.Value;
                }
                else
                {
                    if (result.ContainsKey(pair.Key)) continue;
                    result[pair.Key] = pair.Value;
                }
            }
            return result;
        }

        public static String Render(this NameValueCollection collection)
        {
            if (collection == null) throw new ArgumentNullException("collection");
            var builder = new StringBuilder();
            foreach (string key in collection)
            {
                if (builder.Length > 0) builder.Append('&');
                var values = collection.GetValues(key);
                foreach (var value in values)
                {
                    builder.AppendFormat("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value ?? String.Empty));
                }
            }
            return builder.ToString();
        }

        public static Func<Object> RedirectParam = () =>
                                                       {
                                                           var redirect = HttpContext.Current.Request["Redirect"];
                                                           if (String.IsNullOrEmpty(redirect))
                                                           {
                                                               redirect = HttpContext.Current.Request["ReturnUrl"];
                                                           }
                                                           return new {ReturnUrl = redirect, Redirect = "~" + redirect};
                                                       };

        private const String ControllerPostfix = "Controller";

        public static String Route<TController>()
        {
            return Route<TController>(null);
        }

        public static String Route<TController>(String action)
        {
            return Route<TController>(action, null);
        }

        public static String Route<TController>(Object parameters)
        {
            return Route<TController>(String.Empty, parameters);
        }

        public static RouteValueDictionary GetRouteValues(Object parameters)
        {
            RouteValueDictionary values;
            if (parameters == null)
            {
                values = new RouteValueDictionary();
            }
            else if (parameters is RouteValueDictionary)
            {
                values = (RouteValueDictionary)parameters;
            }
            else if (parameters is IDictionary<String, Object>)
            {
                values = new RouteValueDictionary((IDictionary<String, Object>)parameters);
            }
            else
            {
                values = new RouteValueDictionary(parameters);
            }
            var context = HttpContext.Current;
            if (context != null)
            {
                var request = context.Request;
                foreach (var pair in values)
                {
                    if (ReferenceEquals(GetParam, pair.Value))
                    {
                        values[pair.Key] = request.QueryString[pair.Key];
                    }
                    else if (ReferenceEquals(PostParam, pair.Value))
                    {
                        values[pair.Key] = request.Form[pair.Key];
                    }
                    else if (ReferenceEquals(RequestParam, pair.Value))
                    {
                        values[pair.Key] = request[pair.Key];
                    }
                }
            }

            return values;
        }

        public static String Route<TController>(String action, Object parameters)
        {
            var values = GetRouteValues(parameters);
            var controllerName = typeof(TController).Name;
            if (controllerName.EndsWith(ControllerPostfix, StringComparison.OrdinalIgnoreCase)) controllerName = controllerName.Substring(0, controllerName.Length - ControllerPostfix.Length);
            values["Controller"] = controllerName;
            if (!String.IsNullOrEmpty(action)) values["Action"] = action;
            var virtualPath = RouteTable.Routes.GetVirtualPath(null, "Default", values);
            return virtualPath == null ? "/" : virtualPath.VirtualPath;
        }

        public static String Route()
        {
            return Route(String.Empty);
        }

        public static String Route(String page)
        {
            return Route(page, null);
        }

        public static String Route(Object parameters)
        {
            return Route(String.Empty, parameters);
        }

        public static String Route(String page, Object parameters)
        {
            var values = GetRouteValues(parameters);
            if (String.IsNullOrEmpty(page)) page = "Home";
            values["page"] = page;
            var virtualPath = RouteTable.Routes.GetVirtualPath(null, page.EndsWith("/") ? "StaticFolder" : "StaticPage", values);
            return virtualPath == null ? "/" : virtualPath.VirtualPath;
        }

        public static String ToRoute(this Object parameters, String page)
        {
            return Route(page, parameters);
        }

        public static ActionResult Redirect(this HttpRequestBase request, String path)
        {
            if (request.IsAjaxRequest())
            {
                if (request.ContentType.Contains("linq-model-request")) return ModelResult(new {type = "redirect", path});
            }
            return new RedirectResult(path);
        }

        public static ActionResult Open(this HttpRequestBase request, String path, String target = "_blank", String features = "", Object parameters = null)
        {
            if (request.IsAjaxRequest())
            {
                return ModelResult(new {type = "open", path, target, features, parameters});
            }
            var html = new StringBuilder();
            html.AppendFormat("<script type='text/javascript'>window.open({0}, {1} || '_blank', {2});</script>", path.Json(true), target.Json(true), features.Json(true));
            return new ContentResult {Content = html.ToString(), ContentEncoding = Encoding.UTF8, ContentType = "text/html"};
        }

        public static JsonResult ModelResult(Object parameters)
        {
            return ModelResult(parameters, JsonRequestBehavior.AllowGet);
        }

        public static JsonResult ModelResult(Object parameters, JsonRequestBehavior behavior)
        {
            var p = GetRouteValues(parameters);
            p["__isModelResult"] = true;
            return new JsonResult {Data = new Dictionary<String, Object>(p), JsonRequestBehavior = behavior, ContentEncoding = Encoding.UTF8};
        }

        public static TType To<TType>(this Object value, TType def = default(TType))
        {
            if(value == null || value is DBNull) return def;
            try
            {
                if (typeof(TType) == typeof(Guid) && value is String)
                {
                    value = new Guid(value.ToString());
                    return (TType) value;
                }
                return (TType)Convert.ChangeType(value, typeof (TType));
            }
            catch (Exception)
            {
                return def;
            }
        }
    }
}